package com.lianqi.emcpframework.design.application;

import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lianqi.emcpframework.core.exception.EmcpException;
import com.lianqi.emcpframework.core.exception.ExcepFactor;
import com.lianqi.emcpframework.design.domain.aggregate.Aggregate;
import com.lianqi.emcpframework.design.domain.aggregate.AggregateRepository;
import com.lianqi.emcpframework.design.domain.event.DomainEventPublisher;
import com.lianqi.emcpframework.design.support.AbstractEntity;
import com.google.common.base.Preconditions;

import lombok.Value;

/**
 * @program emcp-framework
 * @ClassName AbstractApplication
 * @description: 抽象应用服务定义
 * @author: sky
 * @create: 2019/06/08 19:24
 */
public abstract class AbstractApplication implements Application {

    private final Logger logger;

    protected Logger logger() {
        return logger;
    }

    protected AbstractApplication(Logger logger) {
        this.logger = logger;
    }

    /**
     * 构造方法
     */
    protected AbstractApplication() {
        this.logger = LoggerFactory.getLogger(getClass());
    }

    protected <ID, T, A extends Aggregate<T, ID>> Creator<ID, T, A>
        creatorFor(AggregateRepository<ID, T, A> repository) {
        return new Creator<>(repository);
    }

    protected <ID, T, A extends Aggregate<T, ID>> Updater<ID, T, A>
        updaterFor(AggregateRepository<ID, T, A> repository) {
        return new Updater<>(repository);
    }

    protected <ID, T, A extends Aggregate<T, ID>> Syncer<ID, T, A> syncerFor(AggregateRepository<ID, T, A> repository) {
        return new Syncer<>(repository);
    }

    /**
     * 聚合根构造器
     * 
     * @param <ID>
     *            主键ID
     * @param <T>
     *            实体泛型
     * @param <A>
     *            聚合根泛型
     */
    protected class Creator<ID, T, A extends Aggregate<T, ID>> {
        /**
         * 聚合根持久化实体
         */
        private final AggregateRepository<ID, T, A> aggregateRepository;
        /**
         * 构造函数
         */
        private Supplier<A> instanceFun;
        /**
         * 控制器
         */
        private Consumer<A> monitor = a -> {
            if (a instanceof AbstractEntity) {
                ((AbstractEntity)a).prePersist();
            }
        };

        /**
         * 事件发布器
         */
        private DomainEventPublisher eventPublisher;

        /**
         * 成功函数
         */
        private Consumer<A> successFun = a -> logger.info("success to save {}", a);

        /**
         * 失败函数
         */
        private BiConsumer<A, Exception> errorFun = (a, e) -> {
            logger.error("failed to save {}.", a, e);
            if (e instanceof RuntimeException) {
                throw (RuntimeException)e;
            } else {
                throw new EmcpException(ExcepFactor.E_ENTITY_CREATE_ERROR, e.toString());
            }
        };

        public Creator(AggregateRepository<ID, T, A> repository) {
            Preconditions.checkArgument(repository != null);
            this.aggregateRepository = repository;
        }

        /**
         * 生成实例
         * 
         * @param instanceFun
         * @return
         */
        public Creator<ID, T, A> instance(Supplier<A> instanceFun) {
            Preconditions.checkArgument(instanceFun != null);
            this.instanceFun = instanceFun;
            return this;
        }

        /**
         * 控制器执行 属性设定
         * 
         * @param monitor
         * @return
         */
        public Creator<ID, T, A> monitor(Consumer<A> monitor) {
            Preconditions.checkArgument(monitor != null);
            this.monitor = this.monitor.andThen(monitor);
            return this;
        }

        /**
         * 发布领域事件
         * 
         * @param publisher
         * @return
         */
        public Creator<ID, T, A> publishBy(DomainEventPublisher publisher) {
            Preconditions.checkArgument(publisher != null);
            this.eventPublisher = publisher;
            return this;
        }

        /**
         * 成功事件执行
         * 
         * @param onSuccessFun
         * @return
         */
        public Creator<ID, T, A> onSuccess(Consumer<A> onSuccessFun) {
            Preconditions.checkArgument(onSuccessFun != null);
            this.successFun = onSuccessFun.andThen(this.successFun);
            return this;
        }

        /**
         * 失败事件执行
         * 
         * @param errorFun
         * @return
         */
        public Creator<ID, T, A> onError(BiConsumer<A, Exception> errorFun) {
            Preconditions.checkArgument(errorFun != null);
            this.errorFun = errorFun.andThen(this.errorFun);
            return this;
        }

        /**
         * 主体方法
         * 
         * @return
         */
        public A call() {
            Preconditions.checkArgument(this.instanceFun != null, "instance fun can not be null");
            Preconditions.checkArgument(this.aggregateRepository != null, "repository can not be null");
            A a = null;
            try {
                a = this.instanceFun.get();
                this.monitor.accept(a);
                this.aggregateRepository.save(a);
                if (this.eventPublisher != null) {
                    this.eventPublisher.publishAll(a.getEvents());
                    a.cleanEvents();
                }
                this.successFun.accept(a);
            } catch (Exception e) {
                this.errorFun.accept(a, e);
            }
            return a;
        }
    }

    /**
     * 聚合根修改类
     * 
     * @param <ID>
     *            主键ID
     * @param <T>
     *            实体泛型
     * @param <A>
     *            聚合根泛型
     */
    protected class Updater<ID, T, A extends Aggregate<T, ID>> {
        /**
         * 聚合根持久化实体
         */
        private final AggregateRepository<ID, T, A> aggregateRepository;
        /**
         * 唯一主键
         */
        private ID id;
        /**
         * 实例加载函数
         */
        private Supplier<Optional<A>> loader;
        private Consumer<ID> onNotExistsFun = id -> {
            throw new EmcpException(ExcepFactor.E_ENTITY_NOT_FOUND, "entity not exists id: " + id);
        };
        /**
         * 控制器
         */
        private Consumer<A> monitor = a -> {
            if (a instanceof AbstractEntity) {
                ((AbstractEntity)a).preUpdate();
            }
        };
        /**
         * 事件发布器
         */
        private DomainEventPublisher eventPublisher;
        /**
         * 成功函数
         */
        private Consumer<Data> successFun = a -> logger.info("success to save {}", a);
        /**
         * 失败函数
         */
        private BiConsumer<Data, Exception> errorFun = (a, e) -> {
            logger.error("failed to save {}.", a, e);
            if (e instanceof RuntimeException) {
                throw (RuntimeException)e;
            } else {
                throw new EmcpException(ExcepFactor.E_ENTITY_UPDATE_ERROR, e.toString());
            }
        };

        public Updater(AggregateRepository<ID, T, A> repository) {
            Preconditions.checkArgument(repository != null);
            this.aggregateRepository = repository;
        }

        /**
         * 设置实体ID
         * 
         * @param id
         * @return
         */
        public Updater<ID, T, A> id(ID id) {
            Preconditions.checkArgument(id != null);
            this.id = id;
            return this;
        }

        /**
         * 生成实例
         * 
         * @param loader
         * @return
         */
        public Updater<ID, T, A> loader(Supplier<Optional<A>> loader) {
            Preconditions.checkArgument(loader != null);
            this.loader = loader;
            return this;
        }

        /**
         * 控制器执行 属性设定
         * 
         * @param monitor
         * @return
         */
        public Updater<ID, T, A> monitor(Consumer<A> monitor) {
            Preconditions.checkArgument(monitor != null);
            this.monitor = this.monitor.andThen(monitor);
            return this;
        }

        /**
         * 发布领域事件
         * 
         * @param publisher
         * @return
         */
        public Updater<ID, T, A> publishBy(DomainEventPublisher publisher) {
            Preconditions.checkArgument(publisher != null);
            this.eventPublisher = publisher;
            return this;
        }

        /**
         * 成功事件执行
         * 
         * @param onSuccessFun
         * @return
         */
        public Updater<ID, T, A> onSuccess(Consumer<Data> onSuccessFun) {
            Preconditions.checkArgument(onSuccessFun != null);
            this.successFun = onSuccessFun.andThen(this.successFun);
            return this;
        }

        /**
         * 失败事件执行
         * 
         * @param errorFun
         * @return
         */
        public Updater<ID, T, A> onError(BiConsumer<Data, Exception> errorFun) {
            Preconditions.checkArgument(errorFun != null);
            this.errorFun = errorFun.andThen(this.errorFun);
            return this;
        }

        /**
         * 主体方法
         * 
         * @return
         */
        public A call() {
            Preconditions.checkArgument(this.aggregateRepository != null, "repository can not be null");
            Preconditions.checkArgument(this.loader != null || this.id != null, "id and loader can not both be null");

            A a = null;
            try {
                if (id != null && loader != null) {
                    throw new RuntimeException("id and loader can both set");
                }
                if (id != null) {
                    this.loader = () -> this.aggregateRepository.findById(this.id);
                }
                Optional<A> aOptional = this.loader.get();
                if (!aOptional.isPresent()) {
                    this.onNotExistsFun.accept(id);
                }
                a = aOptional.get();

                this.monitor.accept(a);

                this.aggregateRepository.update(a);

                if (this.eventPublisher != null) {
                    this.eventPublisher.publishAll(a.getEvents());
                    a.cleanEvents();
                }
                this.successFun.accept(new Data(id, a));
            } catch (Exception e) {
                this.errorFun.accept(new Data(id, a), e);
            }
            return a;
        }

        @Value
        class Data {
            private final ID id;
            private final A a;
        }
    }

    /**
     * 创建、更新支持
     * 
     * @param <ID>
     * @param <T>
     * @param <A>
     */
    protected class Syncer<ID, T, A extends Aggregate<T, ID>> {
        /**
         * 聚合根持久化实体
         */
        private final AggregateRepository<ID, T, A> aggregateRepository;

        /**
         * 实例函数
         */
        private Supplier<A> instanceFun;

        /**
         * 实例加载函数
         */
        private Supplier<Optional<A>> loadFun;

        /**
         * 更新实体执行
         */
        private Consumer<A> whenUpdate = a -> {
            if (a instanceof AbstractEntity) {
                ((AbstractEntity)a).preUpdate();
            }
        };

        /**
         * 创建实体执行
         */
        private Consumer<A> whenCreate = a -> {
            if (a instanceof AbstractEntity) {
                ((AbstractEntity)a).prePersist();
            }
        };

        /**
         * 发布事件
         */
        private DomainEventPublisher eventPublisher;

        private Consumer<Data> successFun = a -> logger.info("success to sync {}", a);
        private BiConsumer<Data, Exception> errorFun = (a, e) -> {
            logger.error("failed to sync {}", a, e);
            if (e instanceof RuntimeException) {
                throw (RuntimeException)e;
            } else {
                throw new EmcpException(ExcepFactor.E_DEFAULT, e.toString());
            }
        };

        Syncer(AggregateRepository<ID, T, A> aggregateRepository) {
            this.aggregateRepository = aggregateRepository;
        }

        /**
         * 生成实例
         * 
         * @param instanceFun
         * @return
         */
        public Syncer<ID, T, A> instance(Supplier<A> instanceFun) {
            Preconditions.checkArgument(instanceFun != null);
            this.instanceFun = instanceFun;
            return this;
        }

        /**
         * 加载实例
         * 
         * @param loadFun
         * @return
         */
        public Syncer<ID, T, A> loadBy(Supplier<Optional<A>> loadFun) {
            Preconditions.checkArgument(loadFun != null);
            this.loadFun = loadFun;
            return this;
        }

        /**
         * 更新实例执行
         * 
         * @param update
         * @return
         */
        public Syncer<ID, T, A> whenUpdate(Consumer<A> update) {
            Preconditions.checkArgument(update != null);
            this.whenUpdate = update.andThen(whenCreate);
            return this;
        }

        /**
         * 创建实例执行
         * 
         * @param create
         * @return
         */
        public Syncer<ID, T, A> whenCreate(Consumer<A> create) {
            Preconditions.checkArgument(create != null);
            this.whenCreate = create.andThen(whenCreate);
            return this;
        }

        /**
         * 发布领域事件
         * 
         * @param publisher
         * @return
         */
        public Syncer<ID, T, A> publishBy(DomainEventPublisher publisher) {
            Preconditions.checkArgument(publisher != null);
            this.eventPublisher = publisher;
            return this;
        }

        /**
         * 成功事件执行
         * 
         * @param onSuccessFun
         * @return
         */
        public Syncer<ID, T, A> onSuccess(Consumer<Data> onSuccessFun) {
            Preconditions.checkArgument(onSuccessFun != null);
            this.successFun = onSuccessFun.andThen(this.successFun);
            return this;
        }

        /**
         * 失败事件执行
         * 
         * @param errorFun
         * @return
         */
        public Syncer<ID, T, A> onError(BiConsumer<Data, Exception> errorFun) {
            Preconditions.checkArgument(errorFun != null);
            this.errorFun = errorFun.andThen(this.errorFun);
            return this;
        }

        public A call() {
            Preconditions.checkArgument(this.aggregateRepository != null, "repository can not be null");
            Preconditions.checkArgument(this.loadFun != null, "load fun can not be null");
            Preconditions.checkArgument(this.instanceFun != null, "instance fun can not be null");
            A a = null;
            Action action = null;
            try {
                Optional<A> aOptional = this.loadFun.get();
                if (aOptional.isPresent()) {
                    a = aOptional.get();
                    whenUpdate.accept(a);
                    this.aggregateRepository.update(a);
                    if (this.eventPublisher != null) {
                        eventPublisher.publishAll(a.getEvents());
                        a.cleanEvents();
                    }
                    action = Action.UPDATE;
                    this.successFun.accept(new Data(a.identity(), action, a));
                } else {
                    a = this.instanceFun.get();
                    whenCreate.accept(a);
                    this.aggregateRepository.save(a);
                    if (this.eventPublisher != null) {
                        eventPublisher.publishAll(a.getEvents());
                        a.cleanEvents();
                    }
                    action = Action.CREATE;
                    this.successFun.accept(new Data(a.identity(), action, a));
                }
            } catch (Exception e) {
                this.errorFun.accept(new Data(null, action, a), e);
            }
            return a;
        }

        @Value
        class Data {
            private final ID id;
            private final Action action;
            private final A a;
        }
    }

    enum Action {
        /**
         * 新增
         */
        CREATE,
        /**
         * 删除
         */
        UPDATE
    }

}
